# path = "C:\pythonWD\strategy_backtesting"
# os.chdir(path)
from tools.collect_data_to_local_parralel import exchange
from utility.algo import Algorithm
from utility.algo2 import Algorithm

from utility.objects import *
import pandas as pd
from datetime import datetime, timedelta
from scipy import stats
from multiprocessing import Queue
from pathlib import Path
import os


class CTA_PROD(Algorithm):
    double_thrust_count = 0
    exit_mode = 0

    entry_flag = False
    entry_price = None
    stop_loss_price = None
    take_profit_price = None
    trailling_price = None

    def run(self):
        msg = "running strategy" + self.name
        print(msg)
        self.init()
        self.hours()
        self.fraction_loop()
        num_txns = len(self.transactions)

        trading_days = self.symbol.data.mkt_day.shape[0]
        msg = f"looping finished with {num_txns} transactions"
        print(msg)
        self.update_dailyEq()
        self.cal_performance()
        daily_txns = num_txns / trading_days
        msg = f"daily_txns: {daily_txns}。"
        print(msg)

        self.win_loss_analysis()

        print(f"double thrust:{self.double_thrust_count}")
        # self.display()

    def analysis(self):
        exit_sigs = self.transactions[self.transactions.flag == "exit"].index
        txns = self.transactions.loc[exit_sigs]
        buy_txns = txns[txns.txn_qty < 0]
        sell_txns = txns[txns.txn_qty > 0]

        for data in [buy_txns, sell_txns]:
            output = pd.DataFrame()

            msg = data["msg"][0]
            PLs = data["p&l"]
            winPLs = PLs[PLs > 0]
            lossPLs = PLs[PLs < 0]

            output.loc[0, 'msg'] = msg
            output.loc[0, 'win_ratio'] = round(len(winPLs) / data.shape[0], 3)
            output.loc[0, 'average_win'] = round(winPLs.mean(), 3)
            output.loc[0, 'average_loss'] = round(lossPLs.mean(), 3)
            output.loc[0, 'win_loss_ratio'] = round(winPLs.mean() / lossPLs.mean() * -1, 3)

            print(output.T)

    def run_port(self):
        msg = "running strategy" + self.name
        print(msg)
        self.init()
        self.hours()
        # return
        self.fraction_loop()
        self.update_dailyEq()

        output = {}
        output["name"] = self.name
        output["allocation"] = self.init_eq
        output["transactions"] = self.transactions
        output["daily_eq"] = self.daily_eq
        output["end_equity"] = self.end_equity

        # output["totalFees"] = self.totalFees
        return (output)

    def init(self):
        self.Amount = self.params["init_equity"]
        self.initEq = self.params["init_equity"]

    def hours(self):
        n1 = self.n
        n2 = n1 * 2

        raw_vol_1 = pd.Series(self.symbol.data.mkt_period['close'].rolling(n1).std(ddof=1) / np.sqrt(n1))
        raw_vol_2 = pd.Series(self.symbol.data.mkt_period['close'].rolling(n2).std(ddof=1) / np.sqrt(n2))

        self.vol_1 = raw_vol_1.rolling(int(n1 / 2)).mean()
        self.vol_2 = raw_vol_2.rolling(int(n2 / 2)).mean()

        # self.vol_1 = raw_vol_1.rolling(int(n1)).mean()
        # self.vol_2 = raw_vol_2.rolling(int(n2)).mean()

        # self.vol_1 = raw_vol_1
        # self.vol_2 = raw_vol_2
        mkt_hour = self.symbol.data.mkt_period

        sig = pd.Series(data=0, index=self.vol_1.index)

        sig[self.vol_1 > self.vol_2] = 1
        sig[self.vol_1 <= self.vol_2] = -1

        sig = sig.dropna()

        mkt_hour["vol_1"] = self.vol_1
        mkt_hour["vol_2"] = self.vol_2
        mkt_hour["sig"] = sig

        self.mkt_hour = mkt_hour

        # build fractions
        col_list = ["date_start", "date_end", "dir", "length", "sp", "rsquare", "open", "high", "low", "close", "range",
                    "vol"]
        # fractions = pd.DataFrame(columns=col_list)
        start = n2 + int(n2 / 2) - 1
        fractions = []

        for i in range(1, len(sig) - 2):
            if sig[i - 1] * sig[i] < 0:
                date_start = sig.index[start]
                date_end = sig.index[i + 1]
                dir = int(sig[i - 1])
                length = i - start + 1

                # calculate mkt_temp
                mkt_temp = self.symbol.data.mkt_period[date_start:date_end]
                mkt_temp = mkt_temp.drop(mkt_temp.index[0])
                if len(mkt_temp) < self.min_length:
                    continue

                mkt_long = self.symbol.data.mkt_period[:date_end]
                mkt_long = mkt_long.iloc[-self.n * 4:-1, ]
                range_long = (mkt_long.high.max() - mkt_long.low.min()) / mkt_long.close[-1]

                vol_long = range_long

                close = mkt_temp.close[len(mkt_temp) - 2]

                ret_series = (mkt_temp.close - mkt_temp.close.shift(1)) / mkt_temp.close.shift(1)
                ret_series = ret_series.drop(ret_series.index[0])
                raw_vol = np.std(mkt_temp.close, ddof=1)
                vol = raw_vol / mkt_temp.close[-1]
                data = {
                    "date_start": date_start,
                    "date_end": date_end,
                    "dir": dir,
                    "length": length,
                    "close": close,
                    "vol": vol,
                    "vol_long": vol_long
                }
                fractions.append(data)
                start = i + 1

        fractions = pd.DataFrame(fractions)

        fractions.dropna(inplace=True)
        fractions.index = range(len(fractions))

        self.fractions = fractions
        fractions.to_csv(os.path.abspath('.\\output\\') + "\\" + self.name + "fractions_python.csv")
        mkt_hour.to_csv(os.path.abspath('.\\output\\') + "\\" + self.name + "_mkt_hour.csv")

    def fraction_loop(self):

        for i in range(0, len(self.fractions)):

            self.bench_fraction = self.fractions.loc[i]

            vol_c = self.params["vol_c"]
            k1 = self.params["k1"]
            k2 = self.params["k2"]

            bench1 = self.fractions.loc[i, "close"]
            # price_range = self.fractions.loc[i,"range"]
            vol = self.fractions.loc[i, "vol"] + vol_c
            vol = np.sqrt(self.fractions.loc[i, "vol"] * self.fractions.loc[i, "vol_long"] * 0.1)
            # vol = self.fractions.loc[i,"vol_mod"] * 2
            dir = self.fractions.loc[i, "dir"]

            # dir filter
            if self.fraction_filter:
                if dir == 1:
                    self.entry_flag = False
                else:
                    self.entry_flag = True
            else:
                self.entry_flag = True

            # band = (bench1 * vol + price_range)/2
            band = bench1 * (vol * self.vol_mod + self.vol_c)

            self.band = band
            if self.trailling:
                self.trailling_band = min(self.trailling * self.band, self.trailling_max * bench1)

            self.upper_price = bench1 + band * k1
            self.lower_price = bench1 - band * k1

            if self.posn != 0:
                self.enter_i = 0

            if i < len(self.fractions) - 1:
                date_start = self.fractions.loc[i + 1, "date_start"]
                date_end = self.fractions.loc[i + 1, "date_end"]
            else:
                date_start = self.fractions.loc[i, "date_end"]
                # date_end = ""
                date_end = str(self.symbol.data.mkt_data.index[-1])

            mkt_min = self.symbol.data.mkt_data[date_start: date_end]
            self.min_loop(mkt_min)

    def min_loop(self, mktdata):
        data = mktdata.loc[mktdata.index[len(mktdata) - 1]]
        self.risk_pos = self.params.get("init_equity") * self.params.get("position_limit")
        # risk_pos = self.params.get("position_limit")

        i = 0
        while i < len(mktdata) - 1:
            # clear posn
            if self.posn != 0 and abs(self.posn) < 0.00000001:
                self.posn = 0

            # for i in range(len(mktdata)-1):
            timestamp = mktdata.index[i]
            nextstamp = mktdata.index[i + 1]
            timegap = pd.Timedelta(seconds=1)
            txndate = timestamp + timegap
            current_price = mktdata.close[i]
            next_price = mktdata.open[i + 1]

            if str(timestamp) == "2020-03-12 18:51:00":
                print(timestamp)

            # trailling
            if self.trailling_price and self.posn != 0:
                if self.posn > 0:
                    if current_price < self.trailling_price:
                        txndate = txndate + timegap
                        price = next_price
                        qty = self.posn * -1
                        fee = self.taker_fee * abs(price * qty)
                        self.add_txn(txndate, qty, price, fee, "exit", "stop trailling")
                        # i = len(mktdata)
                        self.trailling_price = None
                        # i = i + 1
                        i = len(mktdata)
                        continue
                    else:
                        new_price = current_price - self.trailling_band
                        self.trailling_price = max(self.trailling_price, new_price)
                elif self.posn < 0:
                    if current_price > self.trailling_price:
                        txndate = txndate + timegap
                        price = next_price
                        qty = self.posn * -1
                        fee = self.taker_fee * abs(price * qty)
                        self.add_txn(txndate, qty, price, fee, "exit", "stop trailling")
                        # i = len(mktdata)
                        self.trailling_price = None
                        # i = i + 1
                        i = len(mktdata)
                        continue
                    else:
                        new_price = current_price + self.trailling_band
                        self.trailling_price = min(self.trailling_price, new_price)

            # entry_flag
            if not self.entry_flag:
                i = i + 1
                continue

            # exit all
            if self.exit_mode:
                if i > len(mktdata) - 10:
                    if self.posn != 0:
                        txndate = txndate + timegap
                        price = next_price
                        qty = self.posn * -1
                        fee = self.taker_fee * abs(price * qty)
                        self.add_txn(txndate, qty, price, fee, "exit", "exit all")

                    i = len(mktdata)
                    continue

            # double thrust, close position or
            if mktdata.high[i] > self.upper_price and mktdata.low[i] < self.lower_price and self.entry_flag:
                self.double_thrust_count = self.double_thrust_count + 1
                txndate = txndate + timegap
                price = mktdata.open[i]
                fee = self.risk_pos * -1
                self.add_txn(txndate, 0, price, fee, "add_fee")
                i = i + 1
                continue

            # close positions
            if self.posn != 0:
                # exit long short
                if self.posn > 0 and mktdata.low[i] < self.lower_price:
                    txndate = txndate + timegap
                    price = next_price
                    qty = self.posn * -1
                    fee = self.taker_fee * abs(price * qty)
                    self.add_txn(txndate, qty, price, fee, "exit", "exit long")

                if self.posn < 0 and mktdata.high[i] > self.upper_price:
                    txndate = txndate + timegap
                    price = next_price
                    qty = self.posn * -1
                    fee = self.taker_fee * abs(price * qty)
                    self.add_txn(txndate, qty, price, fee, "exit", "exit short")

            # open positions
            if self.posn == 0:
                if mktdata.high[i] > self.upper_price:
                    txndate = txndate + timegap
                    price = next_price
                    # qty = self.risk_pos/price
                    qty = self.risk_pos / (self.upper_price - self.lower_price)
                    fee = self.taker_fee * abs(price * qty)
                    if self.trailling:
                        self.trailling_price = price - self.trailling_band
                    self.add_txn(txndate, qty, price, fee, "enter", "enterlong")

                if mktdata.low[i] < self.lower_price:
                    txndate = txndate + timegap
                    price = next_price
                    # qty = self.risk_pos/price * -1
                    qty = -1 * self.risk_pos / (self.upper_price - self.lower_price)
                    fee = self.taker_fee * abs(price * qty)

                    if self.trailling:
                        self.trailling_price = price + self.trailling_band
                    self.add_txn(txndate, qty, price, fee, "enter", "entershort")

            i = i + 1

    def display(self, log=False):
        import matplotlib.pyplot as plt

        self.algo_performance["PL_per_TXN"] = self.algo_performance.net_pl[0] / self.algo_performance.txns[0]

        print(self.algo_performance.T)
        ax1 = plt.subplot(313)
        ax1.set_title("dailyNetValue")
        ax1.plot(self.daily_net_value.index, self.daily_net_value)

        # ax2 = plt.subplot(312, sharex=ax1)
        # ax2.set_title("position")
        # ax2.plot(self.daily_eq[["position"]])

        data = self.symbol.data.mkt_period
        n = self.n * 2
        n = 24 * 15

        vol_data = data[["close"]].rolling(n).std() / data[["close"]] * np.sqrt(365 * 24 / n)
        vol_data = vol_data.rolling(n).mean()
        ax2 = plt.subplot(312, sharex=ax1)
        # ax2.set_title("position")
        # ax2.plot(self.daily_eq[["position"]])

        ax2.set_title("vol")
        ax2.plot(vol_data)

        # signals
        tran_data = self.transactions['txn_qty']
        buy = tran_data[tran_data > 0].index
        sell = tran_data[tran_data < 0].index

        buy_data = self.transactions['txn_price'][buy]
        sell_data = self.transactions['txn_price'][sell]

        if log:
            mkt_data = np.log2(self.symbol.data.mkt_data[["close"]])
            buy_data = np.log2(buy_data)
            sell_data = np.log2(sell_data)
        else:
            mkt_data = self.symbol.data.mkt_data[["close"]]

        ax3 = plt.subplot(311, sharex=ax1)
        ax3.set_title("mkt_price")
        ax3.plot(mkt_data, color="grey", zorder=1)
        ax3.scatter(buy, buy_data, marker='^', c='r', label='buy', s=30, zorder=10)
        ax3.scatter(sell, sell_data, marker='v', c='g', label='sell', s=30, zorder=10)
        ax3.legend()

        plt.show()


if __name__ == '__main__':
    # prepare symbol data
    begin = datetime(2018, 1, 1)
    end = datetime(2021, 4, 30)

    begin = datetime(2023, 5, 1)
    end = datetime(2024, 5, 30)

    symbol = "BAKEUSDT"
    vol_c = 0.01

    # binance
    exchange='binance'
    data_path = Path.cwd().parent / "data" / exchange / "full_data"
    file_name = f"{symbol.upper()}-spot"
    file_path = data_path / f'{file_name}.csv'

    data = Mktdata(file_path, "1h", begin, end)
    symbol_object = Symbol(symbol, data)

    btc_data = Mktdata(file_path, "1h", begin, end)
    btc = Symbol("btc", btc_data)

    i = 40
    params = {"n": i,
              "k1": 1,
              "k2": 1,
              "position_limit": 0.05,
              "init_equity": 10000,
              "fee_rate": -0.001,
              "taker_fee": -0.001,
              "vol_c": vol_c,
              "vol_mod": 1,
              "min_length": 3,
              "stop_loss": None,
              "take_profit": None,
              "trailling": 4,
              "trailling_max": 0.4,
              "exit_mode": 0,
              "fraction_filter": 1,
              }

    a = CTA_PROD("btc", btc, params, begin, end)
    a.run()
    a.display(True)
    # print(a.transactions)
